#ifndef MODBUS_H
#define MODBUS_H
#include <vector>
#include <algorithm>
#include <functional>
#include <memory>
#include "modbussettings.h"

static inline uint16_t __swap_16(uint16_t num){return (num>>8)|(num<<8);}

#define COIL(n) (TAddress){TAddress::COIL,n}
#define ISTS(n) (TAddress){TAddress::ISTS,n}
#define IREG(n) (TAddress){TAddress::IREG,n}
#define HREG(n) (TAddress){TAddress::HREG,n}
#define BIT_VAL(v)   (v?0xFF00:0x0000)
#define BIT_BOOL(v)  (v==0xFF00)
#define COIL_VAL(v)  (v?0xFF00:0x0000)
#define COIL_BOOL(v) (v==0xFF00)
#define ISTS_VAL(v)  (v?0xFF00:0x0000)
#define ISTS_BOOL(v)  (v==0xFF00)

#define cbDefault nullptr;
struct TRegister;
typedef std::function<uint16_t(TRegister *reg,uint16_t val)> cbModbus;

struct TAddress{
    enum RegType{COIL,ISTS,IREG,HREG};
    RegType type;
    uint16_t address;
    bool operator==(const TAddress &obj) const{ return type==obj.type && address==obj.address;}
    bool operator!=(const TAddress &obj) const{return type !=obj.type || address !=obj.address;}
    TAddress &operator++(){address++;return *this;}
    TAddress operator++(int){TAddress result(*this);++(*this); return result;}
    TAddress &operator+=(const int &inc){address +=inc; return *this;}
    const TAddress operator+(const int &inc)const{TAddress result(*this); result.address +=inc;return result;}
    bool isCoil(){return type==COIL;}
    bool isIsts(){return type==ISTS;}
    bool isIreg(){return type==IREG;}
    bool isHreg(){return type==HREG;}
};
struct TCallback{
    enum CallbackType{ON_SET,ON_GET};
    CallbackType type;
    TAddress address;
    cbModbus cb;
};
struct TRegister{
    TAddress address;
    uint16_t value;
    bool operator==(const TRegister &obj) const{return address ==obj.address;}
};

class Modbus
{
public:
    enum FunctionCode{
        FC_READ_COILS       = 0x01, // Read Coils (Output) Status
        FC_READ_INPUT_STAT  = 0x02, // Read Input Status (Discrete Inputs)
        FC_READ_REGS        = 0x03, // Read Holding Registers
        FC_READ_INPUT_REGS  = 0x04, // Read Input Registers
        FC_WRITE_COIL       = 0x05, // Write Single Coil (Output)
        FC_WRITE_REG        = 0x06, // Preset Single Register
        FC_DIAGNOSTICS      = 0x08, // Not implemented. Diagnostics (Serial Line only)
        FC_WRITE_COILS      = 0x0F, // Write Multiple Coils (Outputs)
        FC_WRITE_REGS       = 0x10, // Write block of contiguous registers
        FC_READ_FILE_REC    = 0x14, // Read File Record
        FC_WRITE_FILE_REC   = 0x15, // Write File Record
        FC_MASKWRITE_REG    = 0x16, // Mask Write Register
        FC_READWRITE_REGS   = 0x17  // Read/Write Multiple registers
    };
    enum ResultCode {
        EX_SUCCESS              = 0x00, // Custom. No error
        EX_ILLEGAL_FUNCTION     = 0x01, // Function Code not Supported
        EX_ILLEGAL_ADDRESS      = 0x02, // Output Address not exists
        EX_ILLEGAL_VALUE        = 0x03, // Output Value not in Range
        EX_SLAVE_FAILURE        = 0x04, // Slave or Master Device Fails to process request
        EX_ACKNOWLEDGE          = 0x05, // Not used
        EX_SLAVE_DEVICE_BUSY    = 0x06, // Not used
        EX_MEMORY_PARITY_ERROR  = 0x08, // Not used
        EX_PATH_UNAVAILABLE     = 0x0A, // Not used
        EX_DEVICE_FAILED_TO_RESPOND = 0x0B, // Not used
        EX_GENERAL_FAILURE      = 0xE1, // Custom. Unexpected master error
        EX_DATA_MISMACH         = 0xE2, // Custom. Inpud data size mismach
        EX_UNEXPECTED_RESPONSE  = 0xE3, // Custom. Returned result doesn't mach transaction
        EX_TIMEOUT              = 0xE4, // Custom. Operation not finished within reasonable time
        EX_CONNECTION_LOST      = 0xE5, // Custom. Connection with device lost
        EX_CANCEL               = 0xE6, // Custom. Transaction/request canceled
        EX_PASSTHROUGH          = 0xE7, // Custom. Raw callback. Indicate to normal processing on callback exit
        EX_FORCE_PROCESS        = 0xE8  // Custom. Raw callback. Indicate to force processing on callback exit
    };
    Modbus();
};

#endif // MODBUS_H
